/*
 * commons is a based project implemented
 * Copyright (C) 2024 Jasmine
 */
package com.easy.web.exception;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.mybatis.spring.MyBatisSystemException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;

import com.easy.core.domain.pojo.ResultBody;
import com.easy.core.exception.GlobalErrorInfoException;
import com.easy.core.exception.ResultInfoInterface;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ValidationException;

/**
 * 统一错误码异常处理 全局异常处理器结合sentinel 全局异常处理器不能作用在 oauth server https://gitee.com/log4j/pig/issues/I1M2TJ
 * <p>
 * 
 * @ResponseStatus(HttpStatus.BAD_REQUEST) @ConditionalOnExpression("!'${security.oauth2.client.clientId}'.isEmpty()")
 *                                         </p>
 * 
 * @author Jasmine
 * @since 1.0.0
 */
@RestControllerAdvice
@ControllerAdvice
public class GlobalErrorInfoHandler {
    private static Logger log = LoggerFactory.getLogger(GlobalErrorInfoHandler.class);

    @Schema(name = "处理业务自定义异常")
    @ExceptionHandler(value = GlobalErrorInfoException.class)
    // @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ResultBody serviceException(GlobalErrorInfoException ex, HandlerMethod handlerMethod) {
        ResultInfoInterface errorInfo = ex.getErrorInfo();
        String stackTrace = ExceptionUtils.getStackTrace(ex);
        log.error("GlobalErrorInfoException code[{}], message[{}], stackTrace[{}],", errorInfo.getCode(),
            errorInfo.getMessage(), stackTrace);
        /*   log.error("[serviceException] class [{}], methodName [{}], code[{}], message[{}]",
            handlerMethod.getBean().getClass(), handlerMethod.getMethod().getName(), ex.getErrorInfo().getCode(),
            ex.getErrorInfo().getMessage());*/
        System.out.println(errorInfo.getCode());
        System.out.println(errorInfo.getMessage());
        ResultBody failed = ResultBody.failed(errorInfo.getCode(), errorInfo.getMessage());
        return failed;
    }

    @Schema(name = "处理Validator校验参数异常")
    @ExceptionHandler(ValidationException.class)
    public ResultBody validationException(ValidationException ex, HandlerMethod handlerMethod) {
        log.error("[ValidationException] class [{}], methodName [{}],  message[{}]", handlerMethod.getBean().getClass(),
            handlerMethod.getMethod().getName(), ex.getMessage());
        return ResultBody.failed(ex.getMessage());
    }

    @Schema(name = "BODY 参数缺失异常(POST方法，比如： User {name:Integer, age:Integer}, 前台传如age为字符串类型)")
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ResultBody handlerHttpMessageNotReadableException(HttpMessageNotReadableException ex,
        HandlerMethod handlerMethod) {
        log.error("[HttpMessageNotReadableException] method [{}],  message[{}]",
            handlerMethod.getBean().getClass() + "#" + handlerMethod.getMethod().getName(), ex.getMessage());
        String errorMessage = "请求体不可为空";
        Throwable cause = ex.getCause();
        if (cause != null) {
            errorMessage = convertMessage(cause);
        }
        return ResultBody.failed(errorMessage);
    }

    @Schema(name = "处理 SpringMVC 请求参数缺失异常，比如接口上设置了 @RequestParam(\"xx\") 参数，结果 xx 参数缺失")
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public ResultBody handlerParamMissingException(MissingServletRequestParameterException ex,
        HandlerMethod handlerMethod) {
        log.error("[MissingServletRequestParameterException] class [{}], methodName [{}], message[{}]",
            handlerMethod.getBean().getClass(), handlerMethod.getMethod().getName(), ex.getMessage());
        return ResultBody.failed(ex.getParameterName() + " 参数缺失");
    }

    @Schema(name = "处理 SpringMVC 请求参数类型错误,比如说接口上设置了 @RequestParam(\"xx\") 参数类型为 Integer，结果传递 xx 参数类型为 String")
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public ResultBody methodArgumentTypeMismatchExceptionHandler(MethodArgumentTypeMismatchException ex,
        HandlerMethod handlerMethod) {
        log.error("[MethodArgumentTypeMismatchException] class [{}], methodName [{}], message[{}]",
            handlerMethod.getBean().getClass(), handlerMethod.getMethod().getName(), ex.getMessage());
        return ResultBody.failed(ex.getPropertyName() + " 请求参数类型错误");
    }

    @Schema(name = "请求参数的校验")
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public ResultBody handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HandlerMethod handlerMethod) {
        log.error("[MethodArgumentNotValidException] class [{}], methodName [{}],  message[{}]",
            handlerMethod.getBean().getClass(), handlerMethod.getMethod().getName(), ex.getMessage());
        StringBuilder sb = new StringBuilder();
        for (FieldError error : ex.getBindingResult().getFieldErrors()) {
            sb.append(error.getDefaultMessage() + ",");
        }
        String str = sb.toString();
        String msg = str.substring(0, str.length() - 1);
        return ResultBody.failed(msg);
    }

    /**
     * 参数绑定异常 表单绑定到 java bean 出错时抛出 BindException 异常 比如： User {name:Integer, age:Integer}, 前台传如 name为字符串类型 GET:
     * http://localhost:8080/user1?age=jasmine&sex=jasmine
     */
    @ExceptionHandler(BindException.class)
    public ResultBody processException(BindException e) {
        log.error(e.getMessage(), e);
        StringBuilder sb = new StringBuilder();
        e.getAllErrors().forEach(error -> {
            if (error instanceof FieldError) {
                FieldError fieldError = (FieldError)error;
                sb.append(fieldError.getField() + fieldError.getDefaultMessage());
            } else {
                sb.append(error.getObjectName() + error.getDefaultMessage());
            }
        });
        return ResultBody.failed(sb.toString());
    }

    @Schema(name = "传参类型错误时，用于消息转换")
    private String convertMessage(Throwable throwable) {
        String error = throwable.toString();
        String regulation = "\\[\"(.*?)\"]+";
        Pattern pattern = Pattern.compile(regulation);
        Matcher matcher = pattern.matcher(error);
        String group = "";
        if (matcher.find()) {
            String matchString = matcher.group();
            matchString = matchString.replace("[", "").replace("]", "");
            matchString = matchString.replaceAll("\\\"", "") + "字段类型错误";
            group += matchString;
        }
        return group;
    }

    @Schema(name = "处理 SpringMVC 请求方法不正确. eg:A 接口的方法为 GET 方式，结果请求方法为 POST 方式，导致不匹配")
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public ResultBody httpRequestMethodNotSupportedExceptionHandler(HttpServletRequest request,
        HttpRequestMethodNotSupportedException ex) {
        String method = request.getMethod();
        String url = request.getRequestURL().toString();
        log.error("HttpRequestMethodNotSupportedException method [{}],url [{}],e [{}]", method, url, ex.getMessage());
        return ResultBody.failed("请求方法不正确");
    }

    /***
     *
     * warn 不能再配置文件中配置，因为这样swagger/doc.html就不能访问了 处理 SpringMVC 请求地址不存在 [404处理] 需要在配置文件中配置
     *
     * #不要为我们工程中的资源文件建立映射 ,出现错误时比如404, 直接抛出异常 #WebMvcAutoConfigurationAdapter.addResourceHandlers
     * #WebProperties.Resources#addMappings spring.web.resources.addMappings=false # 自定义404
     * spring.mvc.throw-exception-if-no-handler-found=true
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ResultBody notFountHandler(HttpServletRequest request, NoHandlerFoundException ex) {
        String method = request.getMethod();
        String url = request.getRequestURL().toString();
        log.error("NoHandlerFoundException method [{}],url [{}],e [{}]", method, url, ex.getMessage());
        return ResultBody.failed("请求地址不存在");
    }

    /**
     * 主键或UNIQUE索引，数据重复异常
     */
    @ExceptionHandler(DuplicateKeyException.class)
    public ResultBody handleDuplicateKeyException(DuplicateKeyException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',数据库中已存在记录'{}'", requestURI, e.getMessage());
        return ResultBody.failed("数据库中已存在该记录，请联系管理员确认!");
    }

    /**
     * Mybatis系统异常 通用处理
     */
    @ExceptionHandler(MyBatisSystemException.class)
    public ResultBody handleCannotFindDataSourceException(MyBatisSystemException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        String message = e.getMessage();
        if (message.contains("CannotFindDataSourceException")) {
            log.error("请求地址'{}', 未找到数据源", requestURI);
            return ResultBody.failed("未找到数据源，请联系管理员确认!");
        }
        log.error("请求地址'{}', Mybatis系统异常", requestURI, e);
        return ResultBody.failed(message);
    }

    @Schema(name = "处理系统异常，兜底处理所有的一切")
    @ExceptionHandler(Exception.class)
    public ResultBody handlerException(Exception ex) {
        String stackTrace = ExceptionUtils.getStackTrace(ex);
        log.error("Exception message[{}], stackTrace[{}]", ex.getMessage(), stackTrace);
        // 处理服务未发现异常
        /*TODO if (ex.getCause() instanceof ClientException) {
            return ResultBody.failed(ResultCode.FAILED.getCode(), "调用失败，请稍后重试");
        }*/
        return ResultBody.failed("系统错误，请联系管理！！！");
    }

}
